﻿/* This file is used to collect PF (Predictive Feature) data for  all our informative moment elements */
/*

These values are calculated here (save processing on server) and the result submitted to the database.

Predictive Features:
====================
A.  LowLevel Motion Characteristics
1.  Total Time              -   Total time, in seconds, that the user opened the specific list selection (IM) until it was closed.                 
2.  YMouse Velocity         -   Divide Selection time by the Y distance moved within the selection list (Distance obtained by counting number of items visited during the list selection and multiplying count with items pixel height. 
3.  YMouse Acceleration     -   YMouse Velocity / Selection Time.
4.  Dwell Time              -   Time spent not moving for a period greater than 1 second.    
B.  Interaction Techniques
5.  Nr Visited Items        -   Count of number of items selected
6.  Unique Items            -   Items selected only once.
7.  Selection Time          -   Time, in seconds, spent within the list to make the final selection.
8.  Average Dwell Time      -   Dwell Time / Nr Visited Items.
C.  Performance  
9.  KLM Ratio
10. KLM Difference    
*/

function AUI() {
    var id;  // insert the name of the drop down list here so it can be identified
    var totalTime, yMouseV, yMouseA, dwellTime;
    var nrVisitedItems, uniqueItemCount, selectionTime, avgDwellTime;
    var KLMRatio, KLMDifference, KLMPredictedTime = 2.65;

    var YDistanceCount;

    var st_totalTime;
    var st_selectionTime;
    var st_dwellTime;

    var instance = this;

    this.setId = function(aId) {
        id = aId;
    }

    this.getId = function() {
        return id;
    }

    this.initialise = function() {
        id = "";
        totalTime = 0;
        yMouseV = 0.0;
        yMouseA = 0.0;
        dwellTime = 0;
        nrVisitedItems = 0;
        selectionTime = 0;
        avgDwellTime = 0;
        KLMRatio = 0.0;
        KLMDifference = 0.0;
        //Look & Mental Operator (LM) + Travel (T) + Click(C)
        //= 1.35 + 1.1 + 0.2 = 2.65
        KLMPredictedTime = 2.65;

        YDistanceCount = 0;

        st_totalTime = new Stopwatch();
        st_selectionTime = new Stopwatch();
        //st_dwellTime = new Stopwatch();
    }

    this.reset = function() {
        id = "";
        totalTime = 0;
        yMouseV = 0;
        yMouseA = 0;
        dwellTime = 0;
        nrVisitedItems = 0;
        selectionTime = 0;
        avgDwellTime = 0;
        KLMRatio = 0;
        KLMDifference = 0;

        YDistanceCount = 0;

        if ((st_totalTime != null) && (st_selectionTime != null)) {
            st_totalTime.clear();
            st_selectionTime.clear();
        } else {
            st_totalTime = new Stopwatch();
            st_selectionTime = new Stopwatch();
        }
    }

    this.restart = function() {
        this.stop();
        this.reset();
        this.start();
    }

    this.start = function() {
        st_totalTime.start();
        st_selectionTime.start();
    }

    this.stop = function() {
        st_totalTime.stop();
        st_selectionTime.stop();

        totalTime = st_totalTime.getSeconds();
        selectionTime = st_selectionTime.getSeconds();
    }

    /***    A.  LowLevel Motion Characteristics    ***/
    this.TotalTime = function() {
        return totalTime;
    }


    this.DwellTime = function() {
        return dwellTime;
    }

    this.YMouseVelocity = function(yDistance, time) {
        if (time == 0)
            return 0;
        else {
            yMouseV = Math.round(yDistance / time);
            return yMouseV;
        }
    }

    this.YMouseV = function() {
        return yMouseV;
    }

    this.YMouseAcceleration = function(distance, time) {
        if (time != 0) {
            YMouseA = Math.round((this.YMouseVelocity(distance, time) / time));
            return YMouseA;
        }
        else {
            return 0;
        }
    }

    this.YMouseA = function() {
        return yMouseA;
    }

    this.IncrDwellTime = function(elapsedTime) {
        if (elapsedTime > 0) {
            dwellTime += elapsedTime;
        }
    }

    /***    B.  Interaction Techniques    ***/

    this.SelectionTime = function() {
        return selectionTime;
    }


    this.AverageDwellTime = function(dTime, nrItems) {
        if ((dTime != 0) && (nrItems != 0)) {
            return (dTime / nrItems);
        } else
            return 0;
    }

    this.AddSelectedToHash = function(selectedItem) {
        if (uniqueItems.has(selectedItem)) {
            //update the value
            var value = uniqueItems.grab(selectedItem);
            value++;
            uniqueItems[selectedItem] = value;
        } else {
            //add new key value pair
            uniqueItems.put(selectedItem, 1);
        }
    }

    this.NrVisitedItems = function() {
        return nrVisitedItems;
    }

    this.IncrVisited = function() {
        nrVisitedItems++;
        return false;
    }

    this.SetNumberOfVisitedItems = function(count) {
        nrVisitedItems = count;
    }

    // Implement with Hash Table
    this.old_UniqueItemsVisited = function() {
        var count = 0;
        for (var i = 0; i < uniqueItems.length; i++) {
            var value = uniqueItems.grab(i);
            if (i == 1) {
                count++;
            }
        }
        return count;
    }

    this.SetUniqueItemsCount = function(count) {
        uniqueItemCount = count;
    }

    this.UniqueItemsVisited = function() {
        return uniqueItemCount;
    }


    /***    B.  Performance    ***/
    this.KLMRatio = function(actualTime) {
        if (actualTime != 0)
            return Math.round(KLMPredictedTime / actualTime);
        else
            return 0;
    }

    this.KLMDifference = function(actualTime) {
        if (actualTime !== 0) {
            var klmd = Math.abs(KLMPredictedTime - actualTime);
            return klmd;
        }
        else
            return 10; //if i return 0, the system will think this user is an 'expert'
    }

    this.update = function(newObject) {
        totalTime += newObject.TotalTime();
        yMouseV = newObject.YMouseV();
        yMouseA = newObject.YMouseA();
        dwellTime = newObject.DwellTime();
        nrVisitedItems = newObject.NrVisitedItems();
        uniqueItemCount = newObject.UniqueItemsVisited();
        selectionTime = newObject.SelectionTime();
        avgDwellTime = newObject.AverageDwellTime();
        KLMRatio = newObject.KLMRatio();
        KLMDifference = newObject.KLMDifference();
        //Look & Mental Operator (LM) + Travel (T) + Click(C)
        //= 1.35 + 1.1 + 0.2 = 2.65
        KLMPredictedTime = 2.65;
    }
}


function Stopwatch() {
    var startTime, endTime, instance = this;

    this.start = function() {
        startTime = new Date();
    };
    this.stop = function() {
        endTime = new Date();
    }
    this.clear = function() {
        startTime = null;
        endTime = null;
    }

    this.elapsed = function() {
        currentTime = new Date();
        return Math.round((currentTime.getTime() - startTime.getTime()) / 1000);
    }

    this.getSeconds = function() {
        if (!endTime) {
            return 0;
        }
        return Math.round((endTime.getTime() - startTime.getTime()) / 1000);
    }
    this.getMinutes = function() {
        return instance.getSeconds() / 60;
    }
    this.getHours = function() {
        return instance.getSeconds() / 60 / 60;
    }
    this.getDays = function() {
        return instance.getHours() / 24;
    }

    this.starttime = function() {
        return instance.startTime;
    }
}


function Hash() {
    this.length = 0;
    this.items = new Array();
    for (var i = 0; i < arguments.length; i += 2) {
        if (typeof (arguments[i + 1]) != 'undefined') {
            this.items[arguments[i]] = arguments[i + 1];
            this.length++;
        }
    }

    this.remove = function(in_key) {
        var tmp_previous;
        if (typeof (this.items[in_key]) != 'undefined') {
            this.length--;
            tmp_previous = this.items[in_key];
            delete this.items[in_key];
        }
        return tmp_previous;
    }

    this.grab = function(in_key) {
        return this.items[in_key];
    }

    this.put = function(in_key, in_value) {
        var tmp_previous;
        if (typeof (in_value) != 'undefined') {
            if (typeof (this.items[in_key]) == 'undefined') {
                this.length++;
            }
            else {
                tmp_previous = this.items[in_key];
            }

            this.items[in_key] = in_value;
        }

        return tmp_previous;
    }

    this.has = function(in_key) {
        return typeof (this.items[in_key]) != 'undefined';
    }

    this.clear = function() {
        for (var i in this.items) {
            delete this.items[i];
        }

        this.length = 0;
    }

    // Emile -
    this.update = function(in_key, value) {
        for (var i in this.items) {
            if (i == in_key) {
                this.remove(i);
                this.put(in_key, value);
            }
        }
        return this;
    }

    this.count = function() {
        return this.items.length;
    }
}